/*
 * Copyright 2003,2004 The Apache Software Foundation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package p.ithorns.framework.common.utils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Modifier;
import java.security.ProtectionDomain;
import java.util.HashMap;
import java.util.Map;

import org.springframework.asm.ClassVisitor;
import org.springframework.asm.Type;
import org.springframework.cglib.core.AbstractClassGenerator;
import org.springframework.cglib.core.ClassEmitter;
import org.springframework.cglib.core.CodeEmitter;
import org.springframework.cglib.core.Constants;
import org.springframework.cglib.core.EmitUtils;
import org.springframework.cglib.core.KeyFactory;
import org.springframework.cglib.core.Local;
import org.springframework.cglib.core.MethodInfo;
import org.springframework.cglib.core.ReflectUtils;
import org.springframework.cglib.core.Signature;
import org.springframework.cglib.core.TypeUtils;

/**
 * @author Chris Nokleberg
 */
abstract public class BeanCopier {

    private static final BeanCopierKey KEY_FACTORY = (BeanCopierKey) KeyFactory.create(BeanCopierKey.class);
    private static final Type CONVERTER = TypeUtils.parseType(BeanConverter.class.getCanonicalName());
    private static final Type BEAN_COPIER = TypeUtils.parseType(BeanCopier.class.getCanonicalName());
    private static final Signature COPY;
    private static final Signature CONVERT;

    static {
        //如果更改了copy的方法签名，记得改这里
        COPY = new Signature("copy", Type.VOID_TYPE, new Type[]{Constants.TYPE_OBJECT, Constants.TYPE_OBJECT, CONVERTER});

        // TypeUtils.parseSignature("Object convert(Object, Class, Object)");
        CONVERT = ReflectUtils.getSignature(BeanConverter.class.getMethods()[0]);
    }


    interface BeanCopierKey {
        public Object newInstance(String source, String target, boolean useConverter);
    }


    public static BeanCopier create(Class source, Class target, boolean useConverter) {
        Generator gen = new Generator();
        gen.setSource(source);
        gen.setTarget(target);
        gen.setUseConverter(useConverter);
        return gen.create();
    }

    abstract public void copy(Object from, Object to, BeanConverter converter);

    public static class Generator extends AbstractClassGenerator<BeanCopier> {
        private static final Source SOURCE = new Source(BeanCopier.class.getName());
        private Class source;
        private Class target;
        private boolean useConverter;

        public Generator() {
            super(SOURCE);
        }

        public void setSource(Class source) {
            if (!Modifier.isPublic(source.getModifiers())) {
                this.setNamePrefix(source.getName());
            }
            this.source = source;
        }

        public void setTarget(Class target) {
            if (!Modifier.isPublic(target.getModifiers())) {
                this.setNamePrefix(target.getName());
            }
            this.target = target;
        }

        public void setUseConverter(boolean useConverter) {
            this.useConverter = useConverter;
        }

        @Override
        protected ClassLoader getDefaultClassLoader() {
            return this.source.getClassLoader();
        }

        @Override
        protected ProtectionDomain getProtectionDomain() {
            return ReflectUtils.getProtectionDomain(this.source);
        }

        public BeanCopier create() {
            Object key = KEY_FACTORY.newInstance(this.source.getName(), this.target.getName(), this.useConverter);
            return (BeanCopier) super.create(key);
        }

        @Override
        public void generateClass(ClassVisitor v) {
            Type sourceType = Type.getType(source);
            Type targetType = Type.getType(target);
            ClassEmitter ce = new ClassEmitter(v);
            ce.begin_class(Constants.V1_8, Constants.ACC_PUBLIC,getClassName(), BeanCopier.BEAN_COPIER, null, Constants.SOURCE_FILE);
            EmitUtils.null_constructor(ce);
            CodeEmitter emitter = ce.begin_method(Constants.ACC_PUBLIC, BeanCopier.COPY, null);
            PropertyDescriptor[] getters = ReflectUtils.getBeanGetters(this.source);
            PropertyDescriptor[] setters = ReflectUtils.getBeanSetters(this.target);

            PropertyDescriptor[] targetGetters = ReflectUtils.getBeanGetters(this.target);
            Map<String,PropertyDescriptor> getterMap = new HashMap<>();
            Map<String,PropertyDescriptor> targetGetterMap = new HashMap<>();

            for (PropertyDescriptor propertyDescriptor : getters) {
                getterMap.put(propertyDescriptor.getName(), propertyDescriptor);
            }
            for (PropertyDescriptor propertyDescriptor : targetGetters) {
                targetGetterMap.put(propertyDescriptor.getName(), propertyDescriptor);
            }

            Local targetLocal = emitter.make_local();
            Local sourceLocal = emitter.make_local();
            if (this.useConverter) {
                emitter.load_arg(1);
                emitter.checkcast(targetType);
                emitter.store_local(targetLocal);
                emitter.load_arg(0);
                emitter.checkcast(sourceType);
                emitter.store_local(sourceLocal);
            } else {
                emitter.load_arg(1);
                emitter.checkcast(targetType);
                emitter.load_arg(0);
                emitter.checkcast(sourceType);
            }

            for (PropertyDescriptor setter : setters) {
                PropertyDescriptor getter = getterMap.get(setter.getName());
                PropertyDescriptor targetGetter = targetGetterMap.get(setter.getName());
                if (getter != null) {
                    MethodInfo read = ReflectUtils.getMethodInfo(getter.getReadMethod());
                    MethodInfo targetRead = ReflectUtils.getMethodInfo(targetGetter.getReadMethod());
                    MethodInfo write = ReflectUtils.getMethodInfo(setter.getWriteMethod());
                    if (this.useConverter) {
                        Type setterType = write.getSignature().getArgumentTypes()[0];
                        //加载局部变量 targetLocal
                        emitter.load_local(targetLocal);
                        //加载方法的第3个参数
                        emitter.load_arg(2);
                        //加载局部变量sourceLocal
                        emitter.load_local(sourceLocal);
                        //执行 局部变量 sourceLocal 的getter方法
                        emitter.invoke(read);
                        //括号？
                        emitter.box(read.getSignature().getReturnType());
                        //加载class变量
                        EmitUtils.load_class(emitter, setterType);
                        //加载String参数
                        emitter.push(write.getSignature().getName());
                        //加载局部变量
                        emitter.load_local(targetLocal);
                        //局部变量执行get方法
                        emitter.invoke(targetRead);
                        //加载 CONVERTER 接口的 CONVERT 方法，注意 CONVERT 中的方法参数
                        emitter.invoke_interface(BeanCopier.CONVERTER, BeanCopier.CONVERT);
                        //关闭括号？
                        emitter.unbox_or_zero(setterType);
                        //执行setter方法，此时栈内变量为 targetLocal
                        emitter.invoke(write);
                    } else if (compatible(getter, setter)) {
                        emitter.dup2();
                        emitter.invoke(read);
                        emitter.invoke(write);
                    }
                }
            }

            emitter.return_value();
            emitter.end_method();
            ce.end_class();
        }

        private static boolean compatible(PropertyDescriptor getter, PropertyDescriptor setter) {
            // TODO: allow automatic widening conversions?
            return setter.getPropertyType().isAssignableFrom(getter.getPropertyType());
        }

        @Override
        protected Object firstInstance(Class type) {
            return ReflectUtils.newInstance(type);
        }

        protected Object nextInstance(Object instance) {
            return instance;
        }
    }

}